/* SBDETECT.C                                                         */
/* Copyright 1997 by Ethan Brodsky.  All Rights Reserved.             */
/* 97/6/30 - ebrodsky@pobox.com - http://www.pobox.com/~ebrodsky/     */

#include <conio.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>

unsigned char read_dsp(int baseio)
  {
    while (!(inp(baseio+0x00E) & 0x80))
      { }

    return(inp(baseio+0x00A));
  }

void write_dsp(int baseio, unsigned char value)
  {
    while ((inp(baseio+0x00C) & 0x80))
      { }

    outp(baseio+0x00C, value);
  }

int reset_dsp(int baseio)
  {
    unsigned i;

    outp(baseio+0x006, 1);
    delay(1);
    outp(baseio+0x006, 0);

    for (i = 65535U; i > 0; i--)
      {
        if ((inp(baseio+0x00E) & 0x80) && (inp(baseio+0x00A) == 0xAA))
          break;
      }

    return(i > 0);
  }

//////////////////////////////////////////////////////////////////////////////

int detect_baseio(void)
  {
   // list of possible addresses, plus "sentinel" -1
    static int val[] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0x280, -1};
    static int count = sizeof(val)/sizeof(*val) - 1; // don't test -1
    int i;

   // for each possible port
    for (i = 0; i < count; i++)
     // test it by attempting to reset the DSP
      if (reset_dsp(val[i]))
       // if found, break now
        break;

   // base io address, or, if not found, -1, since i=count+1
    return val[i];
  }

//////////////////////////////////////////////////////////////////////////////

void dsp_stopall(int baseio)
  {
   // pause 8/16-bit DMA mode digitized sound I/O
    reset_dsp(baseio);
    write_dsp(baseio, 0xD0);
    write_dsp(baseio, 0xD5);
  }

unsigned char dma_req(void)
 // returns dma_request for all channels (bit7->dma7..bit0->dma0)
  {
    return (inp(0xD0) & 0xF0) | (inp(0x08) >> 4);
  }

int bitcount(unsigned char x)
 // returns number of set bits in byte x
  {
    int i;
    int count = 0;

    for (i = 0; i < 8; i++)
      if (x & (1 << i))
        count++;

    return count;
  }

int bitpos(unsigned char x)
 // returns position lowest set bit in byte x (if none, returns -1)
  {
    int i;

    for (i = 0; i < 8; i++)
      if (x & (1 << i))
        return i;

    return -1;
  }

int find_dma(int baseio, int dmac)
  {
   // dma channels active when sound is not: can't be audio DMA channel
    int dma_maskout = ~0x10;  // initially mask only DMA4 (cascade)

   // dma channels active during audio, minus those masked out
    int dma_mask    = 0;

    int i;

   // stop all dsp activity
    dsp_stopall(baseio);

   // poll to find out which DMA channels are in use without sound
    for (i = 0; i < 100; i++)
      dma_maskout &= ~dma_req();

   // now program card and see what channel becomes active
    if (dmac == 1)
      {
       // 8-bit single-cycle DMA mode digitized sound output
        write_dsp(baseio, 0x14);
        write_dsp(baseio, 0x00);  // lo  one sample
        write_dsp(baseio, 0x00);  // hi
      }
    else
      {
       // 16-bit single-cycle DMA mode digitized sound output
        write_dsp(baseio, 0xB0); // 16-bit, D/A, S/C, FIFO off
        write_dsp(baseio, 0x10); // 16-bit mono signed PCM
        write_dsp(baseio, 0x00); // lo   one sample
        write_dsp(baseio, 0x00); // hi
      }

   // poll to find out which (unmasked) DMA channels are in use with sound
    for (i = 0; i < 100; i++)
      dma_mask |= dma_req() & dma_maskout;

   // stop all dsp activity
    dsp_stopall(baseio);

    if (bitcount(dma_mask) == 1)
      return bitpos(dma_mask);
    else
      return -1;

  }

int detect_dma(int baseio)
  {
    return find_dma(baseio, 1);
  }

int detect_dma16(int baseio)
  {
    return find_dma(baseio, 2);
  }

//////////////////////////////////////////////////////////////////////////////

void dsp_transfer(int baseio, int dma8)
  {
    static int dma_pageports[] = {0x87, 0x83, 0x81, 0x82, -1, 0x8B, 0x89, 0x8A};

    int dma_maskport   = 0x0A;
    int dma_modeport   = 0x0B;
    int dma_clrptrport = 0x0C;
    int dma_addrport   = 0x00+2*dma8;
    int dma_countport  = 0x01+2*dma8;
    int dma_pageport   = dma_pageports[dma8];

   // mask DMA channel
    outp(dma_maskport,   0x04 | dma8);

   // write DMA mode: single-cycle read transfer
    outp(dma_modeport,   0x48 | dma8);

   // clear byte-pointer flip-flop
    outp(dma_clrptrport, 0x00);

   // one transfer
    outp(dma_countport,  0x00); // lo
    outp(dma_countport,  0x00); // hi

   // address ????????
    outp(dma_addrport,   0); // lo
    outp(dma_addrport,   0); // hi
    outp(dma_pageport,   0);

   // unmask DMA channel
    outp(dma_maskport,   0x00 | dma8);

   // 8-bit single-cycle DMA mode digitized sound output
    write_dsp(baseio, 0x14);
    write_dsp(baseio, 0x00);    // lo  one sample
    write_dsp(baseio, 0x00);    // hi
  }

static void ((interrupt far *old_handler[16])(void));

static int irq_hit[16];
static int irq_mask[16];

void clear_irq_hit(void)
  {
    int i;

    for (i = 0; i < 16; i++)
      irq_hit[i] = 0;
  }

void interrupt irq2_handler(void)  { irq_hit[2]  = 1;  _chain_intr(old_handler[2]);  }
void interrupt irq3_handler(void)  { irq_hit[3]  = 1;  _chain_intr(old_handler[3]);  }
void interrupt irq5_handler(void)  { irq_hit[5]  = 1;  _chain_intr(old_handler[5]);  }
void interrupt irq7_handler(void)  { irq_hit[7]  = 1;  _chain_intr(old_handler[7]);  }
void interrupt irq10_handler(void) { irq_hit[10] = 1;  _chain_intr(old_handler[10]); }

int detect_irq(int baseio, int dma8)
  {
    int pic1_oldmask, pic2_oldmask;

    int irq = -1;
    int i;

   // install handlers
    old_handler[2]  = _dos_getvect(0x0A);  _dos_setvect(0x0A, irq2_handler);
    old_handler[3]  = _dos_getvect(0x0B);  _dos_setvect(0x0B, irq3_handler);
    old_handler[5]  = _dos_getvect(0x0D);  _dos_setvect(0x0D, irq5_handler);
    old_handler[7]  = _dos_getvect(0x0F);  _dos_setvect(0x0F, irq7_handler);
    old_handler[10] = _dos_getvect(0x72);  _dos_setvect(0x72, irq10_handler);

   // save old IRQ mask and unmask IRQs
    pic1_oldmask = inp(0x21);   outp(0x21, pic1_oldmask & 0x53);
    pic2_oldmask = inp(0xA1);   outp(0xA1, pic2_oldmask & 0xFB);

    clear_irq_hit();

   // wait to see what interrupts are triggered without sound
    delay(100);

   // mask out any interrupts triggered without sound
    for (i = 0; i < 16; i++)
      irq_mask[i] = irq_hit[i];

    clear_irq_hit();

   // try to trigger an interrupt using DSP command F2
    write_dsp(baseio, 0xF2);

    delay(10);

   // detect any interrupts triggered
    for (i = 0; i < 16; i++)
      if (irq_hit[i] && !irq_mask[i])
        irq = i;

   // if F2 fails to trigger an interrupt, run a short transfer
    if (irq == -1)
      {
        reset_dsp(baseio);
        dsp_transfer(baseio, dma8);

        delay(10);

       // detect any interrupts triggered
        for (i = 0; i < 16; i++)
          if (irq_hit[i] && !irq_mask[i])
            irq = i;
      }

   // reset DSP in case we've confused it
    reset_dsp(baseio);

   // remask IRQs
    outp(0x21, pic1_oldmask);
    outp(0xA1, pic2_oldmask);

   // uninstall handlers
    _dos_setvect(0x0A, old_handler[2]);
    _dos_setvect(0x0B, old_handler[3]);
    _dos_setvect(0x0D, old_handler[5]);
    _dos_setvect(0x0F, old_handler[7]);
    _dos_setvect(0x72, old_handler[10]);

    return irq;
  }

//////////////////////////////////////////////////////////////////////////////

int sb_detect(int *baseio, int *irq, int *dma, int *dma16)
  {
    *irq   = -1;
    *dma16 = -1;

    if ((*baseio = detect_baseio()) == -1)
      return 0;

    if ((*dma = detect_dma(*baseio)) == -1)
      return 0;

    if ((*dma16 = detect_dma16(*baseio)) == -1)
      return 0;

    if ((*irq = detect_irq(*baseio, *dma)) == -1)
      return 0;

    return 1;
  }

int main()
  {
    int baseio, irq, dma, dma16;
    int detected;

    detected = sb_detect(&baseio, &irq, &dma, &dma16);

    printf("%s \n", detected ? "Success" : "Failure");

    printf("baseio: %Xh \n", baseio);
    printf("irq:    %i  \n", irq);
    printf("dma:    %i  \n", dma);
    printf("dma16:  %i  \n", dma16);

    return !detected;
  }